package top.maof.mfcoin.block;

import lombok.Data;
import top.maof.mfcoin.tx.*;

import java.math.BigInteger;
import java.util.*;

/**
 * 区块链
 * <p>
 * 单例
 *
 * @author 毛逢
 * @date 2021/11/11 14:09
 */
@Data
public class BlockChain {

    private static BlockChain BLOCK_CHAIN = new BlockChain();

    // 出块时长 10分钟
    public static final long BLOCK_CREATE_TIME = 10 * 60 * 1000;


    // 上一个区块的时间戳
    private long preTimestamp = 0;


    // 初始target
    private BigInteger target = new BigInteger("486604799");


    /**
     * 区块链
     * key: blockHash
     * value: block
     */
    Map<String, Block> blockMap = new HashMap<>();

    /**
     * 链尾，区块链可能会分叉，所以可能会有多个链尾
     * 每条链或是每个分叉都有自己对应的utxo
     * key: blockHash
     * value: utxo
     */
    Map<String, UTXO> latestMap = new HashMap();

    /**
     * 币的产生规则
     */
    public int currentCoin() {
        return 100 + 10 * (blockMap.size() / 1024);
    }


    private BlockChain() {
    }

    public static BlockChain getInstance() {
        return BLOCK_CHAIN;
    }


    /**
     * 添加最新的区块到区块链
     *
     * @author 毛逢
     * @date 2021/11/12 19:48
     */
    public synchronized void appendBlock(Block block) {
        if (!checkLatestBlock(block)) {
            return;
        }
        BlockHeader blockHeader = block.getBlockHeader();
        BlockBody blockBody = block.getBlockBody();

        if (blockHeader.getPreviousHash() == null) {
            // 新链
            UTXO utxo = new UTXO();
            this.blockMap.put(blockHeader.getHash(), block);
            utxo.addBlock(block);
            this.latestMap.put(blockHeader.getHash(), utxo);
        } else {
            // 沿着链的某个分叉进行增长的区块
            if (BLOCK_CHAIN.latestMap.containsKey(blockHeader.getPreviousHash())) {
                // 获取该条分叉对应的utxo的克隆
                UTXO utxo = BLOCK_CHAIN.latestMap.get(blockHeader.getPreviousHash());
                this.blockMap.put(blockHeader.getHash(), block);
                utxo.addBlock(block);
                // 移除旧的链尾
                this.latestMap.remove(blockHeader.getPreviousHash());
                // 添加新的链尾
                this.latestMap.put(blockHeader.getHash(), utxo);
            }
            // 不沿着已有的分叉进行增长,开启了新的分叉
            else {
                // 1 构建新分叉的utxo
                // 1.1 创建新的utxo对象
                UTXO newUtxo = new UTXO();

                // 1.2 获取当前分叉的所有区块
                List<Block> list = new ArrayList<>();

                Block tempBlock = block;

                while (BLOCK_CHAIN.getBlockMap().containsKey(tempBlock.getBlockHeader().getPreviousHash())) {
                    tempBlock = BLOCK_CHAIN.getBlockMap().get(tempBlock.getBlockHeader().getPreviousHash());
                    list.add(tempBlock);
                }

                // 1.3 已将获取到了所有区块,开始构建utxo
                for (int i = 0; i < list.size(); i++) {
                    newUtxo.addBlock(list.get(list.size() - i - 1));
                }
                newUtxo.addBlock(block);

                this.blockMap.put(blockHeader.getHash(), block);

                this.latestMap.put(blockHeader.getHash(), newUtxo);
            }
        }

    }


    /**
     * 验证最新的区块是否合法
     * 包括 blockHeader/blockBody/merkleTree/Tx
     * 这个区块必须要处于区块链链尾
     */
    public static boolean checkLatestBlock(Block block) {

        BlockBody blockBody = block.getBlockBody();
        BlockHeader blockHeader = block.getBlockHeader();

        Block existBlock = BLOCK_CHAIN.blockMap.get(blockHeader.getHash());

        // 该区块是早就存在的区块
        if (existBlock != null && existBlock.getBlockHeader().getIndex() == blockHeader.getIndex()) {
            return false;
        }


        // blockBody被篡改
        if (blockBody.getHashMerkleTree() == null || blockBody.getMerkleTree() == null ||
                !blockBody.getHashMerkleTree().equals(BlockBody.hashMerkleTree(blockBody.getMerkleTree()))) {
            return false;
        }

        // blockHeader被篡改
        if (!blockBody.getHashMerkleTree().equals(blockHeader.getHashMerkleTree()) ||
                !BlockHeader.hashBlock(blockHeader).equals(blockHeader.getHash())) {
            return false;
        }


        // 检查挖矿难度
        BigInteger blockHash = new BigInteger(Base64.getDecoder().decode(blockHeader.getHash()));

        if (blockHeader.getTarget().compareTo(BLOCK_CHAIN.getTarget()) != 0 ||
                blockHash.compareTo(blockHeader.getTarget()) >= 0) {
            return false;
        }


        Map<String, Transaction> tree = blockBody.getMerkleTree();


        // 该区块是第一个区块,第一个区块中只能有一个交易且必须是铸币交易
        if (blockHeader.getPreviousHash() == null) {

            // 不是只有一个交易
            if (tree.size() != 1) {
                return false;
            }

            Set<Map.Entry<String, Transaction>> entries = tree.entrySet();
            for (Map.Entry<String, Transaction> entry : entries) {
                Transaction tx = entry.getValue();
                if (!checkCoinBaseTransaction(tx)) {
                    return false;
                }
            }

            return true;
        }
        // 该区块不是第一个区块
        else {
            // 区块链中没有该区块的上一个区块
            if (!BLOCK_CHAIN.blockMap.containsKey(blockHeader.getPreviousHash())) {
                return false;
            }

            // 沿着链的某个分叉进行增长的区块
            if (BLOCK_CHAIN.latestMap.containsKey(blockHeader.getPreviousHash())) {

                // 获取该条分叉对应的utxo的克隆
                UTXO cloneUtxo = BLOCK_CHAIN.latestMap.get(blockHeader.getPreviousHash()).cloneSelf();
                return checkMerkleTree(tree, cloneUtxo);
            }
            // 不沿着已有的分叉进行增长,开启了新的分叉
            else {
                // 1 构建新分叉的utxo
                // 1.1 创建新的utxo对象
                UTXO newUtxo = new UTXO();
                // 1.2 获取当前分叉的所有区块
                List<Block> list = new ArrayList<>();

                Block tempBlock = block;

                while (BLOCK_CHAIN.getBlockMap().containsKey(tempBlock.getBlockHeader().getPreviousHash())) {
                    tempBlock = BLOCK_CHAIN.getBlockMap().get(tempBlock.getBlockHeader().getPreviousHash());
                    list.add(tempBlock);
                }

                // 1.3 已将获取到了所有区块,开始构建utxo
                for (int i = 0; i < list.size(); i++) {
                    newUtxo.addBlock(list.get(list.size() - i - 1));
                }
                // 2 开始校验

                checkMerkleTree(tree, newUtxo);
            }
        }


        return true;
    }


    /**
     * 校验MerkleTree中交易与交易之间
     * 以及各个交易的合法性
     *
     * @author 毛逢
     * @date 2021/11/13 15:23
     */
    public static boolean checkMerkleTree(Map<String, Transaction> tree, UTXO utxo) {
        Set<Map.Entry<String, Transaction>> entries = tree.entrySet();

        // 纪录铸币交易次数,一个merkleTree中有且只有一次coinBase交易

        int coinBaseCount = 0;

        for (Map.Entry<String, Transaction> entry : entries) {
            Transaction tx = entry.getValue();
            // coinBase交易检查
            if (tx.isCoinBase()) {
                if (coinBaseCount == 1) {
                    return false;
                }
                if (!checkCoinBaseTransaction(tx)) {
                    return false;
                } else {
                    coinBaseCount++;
                }
            }
            // 普通交易检查
            else {
                if (!checkTransaction(tx, utxo)) {
                    return false;
                } else {
                    // 一个普通交易通过检查就需要将交易移除utxo域,以防doubleSpending
                    utxo.getMap().remove(Input.hashInput(tx.getInput()));
                }
            }
        }

        if (coinBaseCount != 1) {
            return false;
        }

        return true;
    }

    /**
     * 校验coinBase交易的正确性
     *
     * @author 毛逢
     * @date 2021/11/13 15:23
     */
    public static boolean checkCoinBaseTransaction(Transaction tx) {
        // 不是铸币交易
        if (!tx.isCoinBase()) {
            return false;
        }

        List<Output> outputs = tx.getOutputs();

        // 不只有一个输出
        if (outputs.size() != 1) {
            return false;
        }

        boolean verify = Transaction.verify(tx);

        // 验证签名
        if (!verify) {
            return false;
        }

        // 铸币交易的币不符合币的产生规则
        if (outputs.get(0).getCoin() != BLOCK_CHAIN.currentCoin()) {
            return false;
        }

        return true;
    }


    /**
     * 校验普通交易的正确性
     * 只能校验单个交易的正确性,无法阻止doubleSpending
     *
     * @author 毛逢
     * @date 2021/11/13 15:23
     */
    public static boolean checkTransaction(Transaction tx, UTXO utxo) {
        // 是否为普通交易
        if (tx.isCoinBase()) {
            return false;
        }

        boolean verify = Transaction.verify(tx);

        //验证签名
        if (!verify) {
            return false;
        }

        String inputHash = Base64.getEncoder().encodeToString(Input.hashInput(tx.getInput()));

        // 币的来源不合法,其中包括utxo中不存在或是utxo中币的个数不对

        if (!utxo.getMap().containsKey(inputHash) ||
                !utxo.getMap().get(inputHash).equals(tx.getInput())) {
            return false;
        }


        List<Output> outputs = tx.getOutputs();

        Set<String> tempSet = new HashSet<>(outputs.size());


        int inputCoin = tx.getInput().getCoin();
        int outputCoin = 0;

        for (Output output : outputs) {
            tempSet.add(output.getAddress());
            outputCoin += output.getCoin();
        }
        // 校验交易输出是否遵循 输出的地址必须唯一，不能出现一次交易中地址重复的现象 规则
        if (tempSet.size() < outputs.size()) {
            return false;
        }

        // 输入的币必须等于输出的币,为了简单,此处不设交易费用
        if (inputCoin != outputCoin) {
            return false;
        }

        return false;
    }


}
